/**
 * 响应式核心
 */
// 当前副作用函数
let activeEffect
// 副作用栈
let effectStack = []
// 全局桶
let bucket = new WeakMap()
/**
 * 注册副作用函数
 * @param fn 函数
 * @param options 可选项
 */
function effect(
  fn,
  options = {
    // 调度器
    scheduler(fn) {
      fn()
    },
    // 是否需要开启懒执行
    lazy: false,
  },
) {
  // 当前函数对象 todo js中一切皆是对象
  let effectFn = () => {
    // 调用cleanup函数完成清除工作
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(activeEffect)
    // 定义一个变量，接收函数返回值
    const res = fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    // 将res作为effectFn的返回值
    return res
  }
  // 将当前选项挂载到副作用对象上
  effectFn.options = options
  // 当前函数对象的依赖关系
  effectFn.deps = []
  // 执行该函数(todo 当需要懒执行时，直接返回该对象，先不执行)
  if (!options.lazy) {
    effectFn()
  }
  return effectFn
}

/**
 * 追踪方法
 * @param target 追踪对象
 * @param key 追踪属性
 */
function track(target, key) {
  if (!activeEffect || !shouldTrack) return
  // 获取该对象副作用Map
  let depsMap = bucket.get(target)
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()))
  }
  let deps = depsMap.get(key)
  if (!deps) {
    depsMap.set(key, (deps = new Set()))
  }
  // 把当前激活的副作用函数添加到依赖集合deps中
  deps.add(activeEffect)
  // 将存在联系的依赖添加进来
  activeEffect.deps.push(deps)
}

/**
 * 触发方法(todo 取出该对象指定属性的Set副作用函数，一一执行)
 * @param target 触发对象
 * @param key 触发属性
 * @param type 操作类型 add | set
 * @param newValue 待设置的新值
 */
function trigger(target, key, type, newValue) {
  let depsMap = bucket.get(target)
  if (!depsMap) {
    return
  }
  // 取出当前对象指定属性的副作用
  const effects = depsMap.get(key)
  // 逐一执行
  const effectsToRun = new Set()
  // 避免副作用自己调用自己造成的死循环
  effects &&
    effects.forEach((effect) => {
      if (activeEffect != effect) {
        effectsToRun.add(effect)
      }
    })
  // 遍历对象属性相关联的副作用(新增或删除时，需要重新触发遍历)
  if (
    type === TriggerType.ADD ||
    type === TriggerType.DELETE ||
    (type === 'SET' && Object.prototype.toString().call(target) === '[object Map]')
  ) {
    const iterateEffects = depsMap.get(ITERATE_KEY)
    iterateEffects &&
      iterateEffects.forEach((effect) => {
        if (effect !== activeEffect) {
          effectsToRun.add(effect)
        }
      })
    const mapIterateEffects = depsMap.get(MAP_KEY_ITERATE_KEY)
    mapIterateEffects &&
      mapIterateEffects.forEach((effect) => {
        if (effect !== activeEffect) {
          effectsToRun.add(effect)
        }
      })
  }
  // 遍历数组类型的副作用
  if (Array.isArray(target) && type == TriggerType.ADD) {
    // 取出与length相关联的副作用函数
    const lengthEffects = depsMap.get('length')
    lengthEffects &&
      lengthEffects.forEach((effect) => {
        if (effect !== activeEffect) {
          effectsToRun.add(effect)
        }
      })
  }
  // 如果操作目标是数组，并且修改了数组的length属性 as: arr.length = 0
  if (Array.isArray(target) && key === 'length') {
    // 对于索引大于或等于新的length,就把关联的副作用函数取出并添加到effectToRun中执行
    depsMap.forEach((effects, key) => {
      if (key >= newValue) {
        effects.forEach((effect) => {
          if (effect !== activeEffect) {
            effectsToRun.add(effect)
          }
        })
      }
    })
  }
  // 逐一执行与当前绑定的副作用函数，如果需要调度执行，则进行调度
  effectsToRun.forEach((effectFn) => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

/**
 * 清除多余的副作用（每次执行副作用前，先清除与当前副作用关联的不必要函数）
 * @param effectFn 当前活跃的副作用函数对象
 */
function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i]
    deps.delete(effectFn)
  }
  effectFn.deps.length = 0
}

// 任务队列
const jobQueue = new Set()
// 用promise解析执行相应操作
const p = Promise.resolve()
// 当前是否正在刷新的标志
let isFlushing = false
/**
 * 刷新工作队列
 */
function flushJob() {
  // 某个任务正在执行的话，其他的任何操作都不会被执行
  if (isFlushing) return
  // 开始执行任务
  isFlushing = true
  p.then(() => {
    // 取出队列中的每一个任务，逐一执行
    jobQueue.forEach((job) => job())
  }).finally(() => {
    // 执行完之后，标记为刷新完成
    isFlushing = false
  })
}

// 计算属性
function computed(getter) {
  // 缓存值
  let value
  // 是否脏值，为真则需要重新计算
  let dirty = true
  // 创建一个懒加载的副作用函数，返回的是函数的值
  const effectFn = effect(getter, {
    lazy: true,
    // 当触发时，会执行该调度器
    scheduler: () => {
      dirty = true
      trigger(obj, 'value')
    },
  })
  // 用对象的get来触发(当获取该函数的值才触发)
  const obj = {
    get value() {
      // todo 需要对值添加缓存功能
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value')
      return value
    },
  }
  return obj
}

/**
 * 获取代理对象
 * @param obj 原对象
 * @returns 代理后的对象
 */
// 唯一键标识
const ITERATE_KEY = Symbol('VUE_PROXY_KEY')
// 触发类型
const TriggerType = {
  SET: 'SET',
  ADD: 'ADD',
  DELETE: 'DELETE',
}
/**
 * 获取响应式对象
 * @param obj 原对象
 * @returns {(string | symbol)[]|boolean|any}
 */
const reactiveMap = new Map()
function reactive(obj) {
  return getProxy(obj)
}

/**
 * 获取代理对象(todo 仅给代理对象添加缓存)
 * @param obj 源对象
 * @param isShallow 是否浅响应
 * @param isReadonly 是否只读
 * @returns {(string|symbol)[]|*|boolean}
 */
function getProxy(obj, isShallow = false, isReadonly = false) {
  const existionProxy = reactiveMap.get(obj)
  if (existionProxy) return existionProxy
  const proxy = createReactive(obj, isShallow, isReadonly)
  reactiveMap.set(obj, proxy)
  return proxy
}

/**
 * 浅响应对象
 * @param obj 原对象
 * @returns {*}
 */
function shallowReactive(obj) {
  return getProxy(obj, true)
}

/**
 * 只读
 * @param obj 原对象
 * @returns {(string|symbol)[]|boolean|*}
 */
function readonly(obj) {
  return getProxy(obj, false, true)
}

/**
 * 浅只读
 * @param obj 原对象
 * @returns {(string|symbol)[]|boolean|*}
 */
function shallowReadonly(obj) {
  return getProxy(obj, true, true)
}

/**
 * 数组方法的重写
 * @type {{}}
 */
// 重新的数组对象
const arrayInstrumentations = {}
;[('includes', 'indexOf', 'lastIndexOf')].forEach((method) => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function (...args) {
    // 代理对象中查找
    let res = originMethod.apply(this, args)
    if (res === false || res === -1) {
      // 原始对象中查找
      res = originMethod.apply(this.raw, args)
    }
    return res
  }
})
// 是否追踪的标志
let shouldTrack = true
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach((method) => {
  const originMethod = Array.prototype[method]
  arrayInstrumentations[method] = function (...args) {
    // 调用之前，禁止跟踪，不准和长度建立联系
    shouldTrack = false
    let res = originMethod.apply(this, args)
    shouldTrack = true
    return res
  }
})
// 集合重写方法
const mutableInstrumentations = {
  // 添加元素
  add(key) {
    const target = this.raw
    const hasKey = target.has(key)
    const res = target.add(key)
    if (!hasKey) {
      trigger(target, key, 'ADD')
    }
    return res
  },
  // 删除元素
  delete(key) {
    const target = this.raw
    const hasKey = target.has(key)
    const res = target.delete(key)
    if (hasKey) {
      trigger(target, key, 'DELETE')
    }
    return res
  },
  // 获取元素
  get(key) {
    const target = this.raw
    const had = target.has(key)
    // 值存在才追踪
    if (had) {
      track(target, key)
      const res = target.get(key)
      // 如果值是一个对象，需要我们再包装一层返回
      return typeof res === 'object' ? reactive(res) : res
    }
  },
  // Map设置元素
  set(key, value) {
    const target = this.raw
    const had = target.has(key)
    const oldValue = target.get(key)
    // todo 将value直接设置到原始对象中，如果此时的value是响应式的，就会造成数据污染的现象
    const rawValue = value.raw || value
    target.set(key, rawValue)
    if (!had) {
      // 值不存在为新增
      trigger(target, key, 'ADD')
    } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
      // 值存在为修改，在此基础上做了NAN值的判断
      trigger(target, key, 'SET')
    }
  },
  // 集合的遍历
  forEach(callback) {
    const wrap = (val) => (typeof val === 'object' ? reactive(val) : val)
    const target = this.raw
    track(target, ITERATE_KEY)
    // 通过原始数据对象调用forEach方法，并把callback传递过去
    target.forEach((v, k) => {
      // 将原始对象包装成代理对象
      callback(thisArg, wrap(v), wrap(k), this)
    })
  },
  // 迭代器方法
  [Symbol.iterator]: iterationMethod,
  // entries
  entries: iterationMethod,
  // values
  values: valuesIterationMethod,
  // keys
  keys: keysIterationMethod,
}
/**
 * 迭代方法
 * @returns {{next(): {value: [((string|symbol)[]|*|boolean),((string|symbol)[]|*|boolean)]|*, done: *}}|{value: (((string|symbol)[]|*|boolean)[]|*), done: *}}
 */
function iterationMethod() {
  const target = this.raw
  const itr = target[Symbol.iterator]()
  const wrap = (val) => (typeof val === 'object' && val !== null ? reactive(val) : val)
  track(target, ITERATE_KEY)
  return {
    // 迭代器协议
    next() {
      const { value, done } = itr.next()
      return {
        value: value ? [wrap(value[0]), wrap(value[1])] : value,
        done,
      }
    },
    // 可迭代协议
    [Symbol.iterator]() {
      return this
    },
  }
}
const MAP_KEY_ITERATE_KEY = Symbol()
function valuesIterationMethod() {
  const target = this.raw
  const itr = target.values()
  const wrap = (val) => (typeof val === 'object' && val !== null ? reactive(val) : val)
  track(target, MAP_KEY_ITERATE_KEY)
  return {
    // 迭代器协议
    next() {
      const { value, done } = itr.next()
      return {
        value: wrap(value),
        done,
      }
    },
    // 可迭代协议
    [Symbol.iterator]() {
      return this
    },
  }
}
function keysIterationMethod() {
  const target = this.raw
  const itr = target.keys()
  const wrap = (val) => (typeof val === 'object' && val !== null ? reactive(val) : val)
  track(target, MAP_KEY_ITERATE_KEY)
  return {
    // 迭代器协议
    next() {
      const { value, done } = itr.next()
      return {
        value: wrap(value),
        done,
      }
    },
    // 可迭代协议
    [Symbol.iterator]() {
      return this
    },
  }
}
/**
 * 创建响应式对象
 * @param obj 源对象
 * @param isShallow 是否浅式
 * @param isReadonly 是否只读
 * @returns {(string|symbol)[]|boolean|*|(string | symbol)[]|boolean}
 */
function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    // 读取
    get(target, key, receiver) {
      // 新增属性返回当前代理对象原型(todo 避免副作用重复执行)
      if (key === 'raw') {
        return target
      }
      // 如果读取的是size属性，则证明是对集合进行代理。是反射的目标对象为源对象
      if (key === 'size') {
        track(target, ITERATE_KEY)
        return Reflect.get(target, key, target)
      }
      // 将方法与原始数据对象target绑定后返回 target.delete
      if (key === 'delete' || key === 'add' || key === 'get') {
        return mutableInstrumentations[key]
        // return target[key].bind(target)
      }
      // 如果是数组且key存在于arrayInstrumentations上，则返回定义在该对象上的值
      if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      // 非只读才跟踪
      if (!isReadonly && typeof key !== 'symbol') {
        // 获取时，跟踪
        track(target, key)
      }
      const res = Reflect.get(target, key, receiver)
      if (isShallow) {
        return res
      }
      // 如果获取到的值是对象，进行二次包装
      if (typeof res === 'object' && res !== null) {
        return isReadonly ? readonly(res) : reactive(res)
      }
      return res
    },
    // 设置
    set(target, key, value, receiver) {
      if (isReadonly) {
        console.warn(`属性${key}是只读的`)
        return true
      }
      const oldValue = target[key]
      // 数组类型 | 对象类型
      // todo 会对length和当前副作用函数建立联系
      const type = Array.isArray(target)
        ? Number(key) > target.length
          ? TriggerType.ADD
          : TriggerType.SET
        : Object.prototype.hasOwnProperty.call(target, key)
        ? TriggerType.SET
        : TriggerType.ADD
      const res = Reflect.set(target, key, value, receiver)
      // 当前原对象是否为代理的元对象
      if (target === receiver.raw) {
        // 设置时，触发更新(只对改变做更新，且类型不为NAN)
        if (oldValue !== value && (oldValue === oldValue || value === value)) {
          // 修改类型、修改的新值
          trigger(target, key, type, value)
        }
      }
      return res
    },
    // 删除属性
    deleteProperty(target, key) {
      if (isReadonly) {
        console.warn(`属性${key}是只读的`)
        return true
      }
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      const res = Reflect.deleteProperty(target, key)
      // 删除成功且属性是对象自身的属性
      if (res && hadKey) {
        trigger(target, key, TriggerType.DELETE)
      }
      return res
    },
    // in 时触发
    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },
    // for in  时触发
    ownKeys(target) {
      // 将副作用与ITERATE_KEY相关联
      track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
  })
}

/**
 * watch函数
 * @param source 源对象
 * @param cb 回调函数
 * @param options 参数
 */
function watch(
  source,
  cb,
  options = {
    // 是否立即执行
    immediate: false,
    // 回调函数会在watch创建时候立即执行一次 pre | post | sync
    flush: undefined,
  },
) {
  let getter
  // 如果是一个getter函数，直接赋值
  if (typeof source === 'function') {
    getter = source
  } else {
    // 递归转对象赋值
    getter = () => traverse(source)
  }
  // 过期的副作用回调
  let cleanup
  function onInvalidate(fn) {
    cleanup = fn
  }
  let oldValue, newValue
  // 提取scheduler 调度函数为一个独立的job函数
  const job = () => {
    newValue = effectFn()
    if (cleanup) {
      cleanup()
    }
    // 数据变化时，调用该回调
    cb(newValue, oldValue, onInvalidate)
    oldValue = newValue
  }
  // 执行该getter {obj:1111,abj:@@@@} | () => { return obj.a + obj.b}
  const effectFn = effect(() => getter(), {
    // 懒加载
    lazy: true,
    // 开启调度
    scheduler: () => {
      // flush 如果为 post，将其放到微任务队列中执行
      if (options.flush === 'post') {
        const p = Promise.resolve()
        p.then(job)
      } else {
        job()
      }
    },
  })
  if (options.immediate) {
    job()
  } else {
    oldValue = effectFn()
  }
}

/**
 * 递归跟踪对象的所有属性值
 * @param value 源对象
 * @param seen Set集合
 */
function traverse(value, seen = new Set()) {
  if (typeof value !== 'object' || value == null || seen.has(value)) {
    return
  }
  seen.add(value)
  for (let k in value) {
    traverse(value[k], seen)
  }
  return value
}

/**
 * 代理普通对象
 * @param val
 * @returns {(string|symbol)[]|*|boolean}
 */
function ref(val) {
  const wrapper = {
    value: val,
  }
  Object.defineProperty(wrapper, '__v_isRef', {
    value: true,
  })
  return reactive(wrapper)
}

/**
 * 转ref
 * @param obj
 * @param key
 * @returns {{readonly value: *}|*}
 */
function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key]
    },
    set value(val) {
      obj[key] = val
    },
  }
  Object.defineProperty(wrapper, '__v_isRef', {
    value: true,
  })
  return wrapper
}

function toRefs(obj) {
  const ret = {}
  for (const key in obj) {
    ret[key] = toRef(obj, key)
  }
  return ret
}

function proxyRefs(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver)
      return value.__v_isRef ? value.value : value
    },
    set(target, key, newValue, receiver) {
      const value = target[key]
      if (value.__v_isRef) {
        value.value = newValue
        return true
      }
      return Reflect.set(target, key, newValue, receiver)
    },
  })
}

// 直接导出
export { computed, watch, reactive, shallowReactive, readonly, shallowReadonly, ref, toRef, toRefs, proxyRefs, effect }
// 导出为对象 export default {}
